In VISUALIZATION VIBES project Study 2, participants completed an attitutde eliciation survey, asking questions about their attitude toward (5) stimulus images (data visualizations). Each participant was randomly assigned to one of 6 stimulus blocks, each containing 1 image from each of (4) ‘embellishment categories’ (ranging from most abstract to most figural). Each participant started by responding to questions for a single ‘common’ stimulus (B0-0). Two participant recruitment pools were used: Prolific, with a smaller set of participants recruited from Tumblr (to replicate and compare survey results to Study 1 interviews with participants sourced from Tumblr).

This notebook contains code to replicate quantitative analysis of data from Study 2 reported in CHI submission #5584.

SETUP

Import Packages

Setup Graphing

# Custom ggplot theme to make pretty plots
# Get the font at https://fonts.google.com/specimen/Barlow+Semi+Condensed
theme_clean <- function() {
  theme_minimal(base_family = "Barlow Semi Condensed") +
    theme(panel.grid.minor = element_blank(),
          plot.title = element_text(family = "BarlowSemiCondensed-Bold"),
          axis.title = element_text(family = "BarlowSemiCondensed-Medium"),
          strip.text = element_text(family = "BarlowSemiCondensed-Bold",
                                    size = rel(1), hjust = 0),
          strip.background = element_rect(fill = "grey80", color = NA))
}

set_theme(base = theme_clean())



############## SETUP Colour Palettes
#https://www.r-bloggers.com/2022/06/custom-colour-palettes-for-ggplot2/

## list of color pallettes
my_colors = list(
  politics = c("#184aff","#5238bf", "#4f4a52" ,"#84649c", "#ff0000"),
  blackred = c("black","red"),
  greys = c("#707070","#999999","#C2C2C2"),
  greens = c("#ADC69D","#81A06D","#567E39","#2D5D16","#193E0A"),
  smallgreens = c("#ADC69D","#567E39","#193E0A"), ## MALE FEMALE OTHER
  olives = c("#CDCEA1","#B8B979","#A0A054","#78783F","#50502A","#35351C"),
  lightblues = c("#96C5D2","#61A2B2","#3C8093","#2C6378","#1F4A64"),
  darkblues = c("#7AAFE1","#3787D2","#2A73B7","#225E96","#1A4974","#133453"),
  reds = c("#D9B8BD","#CE98A2","#B17380","#954E5F","#78263E","#62151F"),
  traffic = c("#CE98A2","#81A06D","yellow"),
  questions = c("#B17380","#3787D2", "#567E39", "#EE897F"),
  tools= c("#D55662","#EE897F","#F5D0AD","#A0B79B","#499678","#2D363A"), #? ... design.....vis...... programming
  encounter = c("#8E8E8E","#729B7D"), ##SCROLL ENGAGE
  actions2 = c("#8E8E8E","#729B7D"),
  actions4 = c("#8E8E8E", "#A3A3A3","#729B7D","#499678"),
  actions3 = c("#8E8E8E","#99b898ff","#fdcea8ff"),
  actions = c("#8E8E8E","#2A363B","#99b898ff","#fdcea8ff","#ff837bff","#e84a60ff"),
  
  platforms = c("#5D93EA","#FF70CD", "#3BD3F5", "#8B69B5","black"),
  amy_gradient =  c("#ac57aa", "#9e5fa4", "#90689f", "#827099", "#747894", "#66818e", "#578988", "#499183", "#3b997d", "#2da278", "#1faa72"),
  my_favourite_colours = c("#702963", "#637029",    "#296370")
)

## function for using palettes
my_palettes = function(name, n, all_palettes = my_colors, type = c("discrete","continuous"), direction = c("1","-1")) {
  palette = all_palettes[[name]]
  if (missing(n)) {
    n = length(palette)
  }
  type = match.arg(type)
  out = switch(type,
               continuous = grDevices::colorRampPalette(palette)(n),
               discrete = palette[1:n]
  )
  out = switch(direction,
               "1" = out,
               "-1" = palette[n:1])
  structure(out, name = name, class = "palette")
}
# ############## RETURNS SD STACKED AND COLORED BY BY X
# ## LOOP STYLE
# multi_sd <- function (data, left, right, x, y, color) {
# 
#   # g <- ggplot(df, aes(y = .data[[x]], x = {{y}}, color = {{color}}))+
#   g <- ggplot(data, aes(y = .data[[x]], x = .data[[y]], color = .data[[color]]))+
#   geom_boxplot(width = 0.5) +
#   geom_jitter(width = 0.1, alpha=0.5) +
#     
#   scale_y_continuous(limits=c(-1,101)) +
#   labs(x="", y="") +
#   coord_flip() +
#   guides(
#     y = guide_axis_manual(labels = left),
#     y.sec = guide_axis_manual(labels = right)
#   ) + theme_minimal()
# 
#   return(g)
# }
# 
# 
# ############## RETURNS SINGLE SD 
# ## LOOP STYLE
# single_sd <- function (data, left, right, x) {
# 
#   g <- ggplot(data, aes(y = {{x}}, x = ""))+
#   geom_boxplot(width = 0.5) +
#   geom_jitter(width = 0.1, alpha=0.5) +
#   scale_y_continuous(limits=c(-1,101)) +
#   labs(x="", y="") +
#   coord_flip() +
#   guides(
#     y = guide_axis_manual(labels = left),
#     y.sec = guide_axis_manual(labels = right)
#   ) + theme_minimal()
# 
#   return(g)
# }


# ######## RETURNS SINGLE SD
# ##  APPLY STYLE
plot_sd = function (data, column, type, mean, facet, facet_by, boxplot, labels) {

  ggplot(data, aes(y = .data[[column]], x="")) +
    {if(boxplot) geom_boxplot(width = 0.5) } +
    geom_jitter(width = 0.1, alpha=0.2, {if(facet) aes(color=.data[[facet_by]])}) +
    {if(mean)
      stat_summary(fun="mean", geom="point", shape=20, size=5, color="blue", fill="blue")
      } +
    {if(mean)
      ## assumes data has been passed in with mean column at m
      # stat_summary(fun="mean", geom="text", colour="blue",  fontface = "bold",
      #            vjust=-1.25, hjust = 0.50, aes( label=round(..y.., digits=0)))
      stat_summary(fun="mean", geom="text", colour="blue",  fontface = "bold",
                 vjust=-1.25, hjust = 0.50, aes( label=round(..y.., digits=0)))
      } +

    {if(facet) facet_grid(.data[[facet_by]] ~ .)} +
    # scale_y_continuous(limits=c(-1,101)) +
    labs(x="", y="") +
    coord_flip()  +
    {if(type == "S")
      guides(
        y = guide_axis_manual(labels = labels[column,"left"]),
        y.sec = guide_axis_manual(labels = labels[column,"right"])
      )} +
    {if(type == "Q")
      guides(
        y = guide_axis_manual(labels = labels[q,"left"]),
        y.sec = guide_axis_manual(labels = labels[q,"right"])
      )} +
  theme_minimal()  +
     labs (
       caption = column
     ) + easy_remove_legend()
}

Import References

############## IMPORT REFERENCE FILES
ref_stimuli <- readRDS("data/input/REFERENCE/ref_stimuli.rds")
ref_surveys <- readRDS("data/input/REFERENCE/ref_surveys.rds")
ref_labels <- readRDS("data/input/REFERENCE/ref_labels.rds")
ref_labels_abs <- readRDS("data/input/REFERENCE/ref_labels_abs.rds")

############## SETUP Graph Labels
ref_stim_id <- levels(ref_stimuli$ID)
ref_cat_questions <- c("MAKER_ID","MAKER_AGE","MAKER_GENDER")
ref_free_response <- c("MAKER_DETAIL", "MAKER_EXPLAIN", "TOOL_DETAIL", "CHART_EXPLAIN")
ref_conf_questions <- c("MAKER_CONF", "AGE_CONF", "GENDER_CONF", "TOOL_CONF")
ref_sd_questions <- rownames(ref_labels)
ref_sd_questions_abs <- rownames(ref_labels_abs)
  

# ref_blocks <- c("block1", "block2", "block3", "block4", "block5", "block6")
ref_blocks <- c(1,2,3,4,5,6)

Import Data

############## IMPORT DATA FILES
# df_data <- readRDS("data/output/df_data.rds") #1 row per participant — WIDE
df_participants <- readRDS("data/output/df_participants.rds") #1 row per participant — demographic
# df_questions <- readRDS("data/output/df_questions.rds") #1 row per question — LONG
# df_sd_questions_wide <- readRDS("data/output/df_sd_questions_wide.rds") # only sd questions WIDE
# 
# 
# df_tools <- readRDS("data/output/df_tools.rds") #multiselect format for tools Question
# df_actions <- readRDS("data/output/df_actions.rds") # multiselect format for action Question
# # # df_graphs_full <- readRDS("data/output/df_graphs_full.rds") #includes free response data
# 
df_graphs <- readRDS("data/output/df_graphs.rds") #only categorical and numeric questions
df_sd_questions_long <- readRDS("data/output/df_sd_questions_long.rds") # only sd questions LONG
# 
# ### DATA FILES WITH (VARIABLE-WISE) Z-SCORED SEMANTIC DIFFERENTIAL QS 
# df_graphs_z <- readRDS("data/output/df_graphs_z.rds") #only categorical and numeric questions
# df_sd_questions_long_z <- readRDS("data/output/df_sd_questions_long_z.rds") # only sd questions LONG
# 
# 
# ### DATA FILES WITH ABSOLUTE VALUE SEMANTIC DIFFERENTIAL QS 
# df_graphs_abs <- readRDS("data/output/df_graphs_abs.rds") #only categorical and numeric questions
# df_sd_questions_long_abs <- readRDS("data/output/df_sd_questions_long_abs.rds") # only sd questions LONG

AGGREGATED DATA

(5.1.2) Survey Response Time

df <- df_participants

## for descriptives paragraph
a.desc.duration <- psych::describe(df %>% pull(duration.min))

As Reported in Section 5.1.2 Procedure :

Across the entire sample, responses from (n = 318 ) participants ranged from 11 to 228 minutes, with a mean response time of 45 minutes, SD = 26.

rm(df, a.desc.duration)

(5.1.4) Participants

df <- df_participants

## FOR DESCRIPTIVES PARAGRAPH
# #PROLIFIC
df.p <- df %>% filter(Distribution == "PROLIFIC")
desc.gender.p <- table(df.p$D_gender) %>% prop.table()
names(desc.gender.p) <- levels(df.p$D_gender)
p_participants <- nrow(df.p)

# #TUMBLR
df.t <- df %>% filter(Distribution == "TUMBLR")
desc.gender.t <- table(df.t$D_gender) %>% prop.table()
names(desc.gender.t) <- levels(df.t$D_gender)
t_participants <- nrow(df.t)

As Reported in Section 5.1.4 Participants :

For Study 2, a total of 318 participants were recruited from US-located English speaking users of TUMBLR (n = 78) and PROLIFIC (n = 240).

78 individuals from Tumblr participated in Study 2, ( 36% Female, 5% Male, 40% Non-binary, 17% Prefer to Self Describe, 3% Prefer Not to Say. Other).

240 individuals from PROLIFIC participated in Study 2, ( 54% Female, 42% Male, 3% Non-binary, 0 % Prefer Not to Say, 0% Prefer to Self Describe).

rm(df, df.p, desc.gender.p, p_participants, df.t, desc.gender.t, t_participants)

(5.1.4) Participants per Block

As Reported in Section 5.1.4 Participants, there were ~ 53 participants per stimulus block

df <- df_participants
table(df$Assigned.Block)
## 
##  1  2  3  4  5  6 
## 55 52 52 54 53 52
# cols = c("Block", "n")
# %>% kable(col.names = cols)

(FIG 5) — Distribution for each Survey Question

As Reported in Figure 5, descriptive statistics for in-scope survey questions.

# # library(tinytable)
# # library(webshot2)

#### CUSTOM HORIZONTAL STACKED BARPLOT
g <- function(d, ...){

  p <- d$pal %>% unique
  ggplot(d, aes(x="", fill=value)) +
    geom_bar(stat="count", position = "stack") +
    scale_fill_manual(values=my_colors[[p]]) +
    coord_flip() + theme_void() + easy_remove_axes() + easy_remove_legend()
}


## SETUP LIST OF NUMERIC DATAFRAMES
all_q <- c("MAKER_CONF", "AGE_CONF", "GENDER_CONF", ref_sd_questions)

# ## SETUP NUMERIC DATAFRAME
df_num <- df_graphs %>% select(all_of(all_q))

## CALC MEANS
### MEANS
m <- sapply(df_num, FUN=mean)
m <- round(m,1)
m <- paste0("M=",m)
sd <- sapply(df_num, FUN=sd)
sd <- round(sd,1)
sd <- paste0("SD=",sd)
stat <- paste0(m," ",sd)


### CREATE LIST OF CATEGORICAL DATAFRAMES
id = df_graphs %>% select(MAKER_ID) %>%
  pivot_longer(cols=1)%>% mutate(pal="reds") %>% as.data.frame()
age = df_graphs %>% select(MAKER_AGE) %>%
  pivot_longer(cols=1)%>% mutate(pal="lightblues") %>% as.data.frame()
gender = df_graphs %>% select(MAKER_GENDER) %>%
  pivot_longer(cols=1)%>% mutate(pal="smallgreens") %>% as.data.frame()

df_cat <- list()
df_cat[["MAKER_ID"]] <- id
df_cat[["MAKER_AGE"]] <- age
df_cat[["MAKER_GENDER"]] <- gender

## CALC CAT PROPORTIONS
n <- nrow(id)
m_id <- table(id) %>% as.data.frame() %>% mutate(prop = round(Freq/n, 2)*100) %>% map_df(rev) #reverse reading order
stat_id <- paste0(m_id$value, "(", m_id$prop,"%)") %>%  unlist() %>% paste0(collapse=''," ")

n <- nrow(age)
m_age <- table(age) %>% as.data.frame() %>% mutate(prop = round(Freq/n, 2)*100)%>% map_df(rev)
stat_age <- paste0(m_age$value, "(", m_age$prop,"%)") %>%  unlist() %>% paste0(collapse=''," ")

n <- nrow(gender)
m_gender <- table(gender) %>% as.data.frame() %>% mutate(prop = round(Freq/n, 2)*100)%>% map_df(rev)
stat_gender <- paste0(m_gender$value, "(", m_gender$prop,"%)")%>%  unlist() %>% paste0(collapse=''," ")

## SETUP QUESTIONS
questions <- c(ref_cat_questions, "MAKER_CONF", "AGE_CONF", "GENDER_CONF", ref_sd_questions) 


#### SETUP TABLE
tab <- data.frame(
  VARIABLE = questions,
  DISTRIBUTION = "",
  STATISTICS = c(stat_id, stat_age, stat_gender, stat)
)

### RENDER TABLE
t <- tinytable::tt(tab, theme = "void") %>%
  plot_tt(j=2, i= 1:3, fun=g, data = df_cat, height = 1.5) %>%
  plot_tt(j=2, i= 4:17, fun="density", data = df_num, color="darkgrey") %>%
  style_tt(j=2, align="c")

t
VARIABLE DISTRIBUTION STATISTICS
MAKER_ID political(16%) news(19%) business(20%) education(25%) organization(7%) individual(12%)
MAKER_AGE gen-z(9%) millennial(40%) gen-x(41%) boomer(10%)
MAKER_GENDER Male(59%) Female(34%) Other(7%)
MAKER_CONF M=61.6 SD=23.4
AGE_CONF M=60 SD=21.3
GENDER_CONF M=54.2 SD=25.4
MAKER_DESIGN M=48.1 SD=28.3
MAKER_DATA M=42.7 SD=27.7
MAKER_POLITIC M=47 SD=18.7
MAKER_ARGUE M=54.9 SD=19.9
MAKER_SELF M=44 SD=19.6
MAKER_ALIGN M=52.7 SD=18.1
MAKER_TRUST M=58 SD=18.6
CHART_TRUST M=54.6 SD=23.2
CHART_INTENT M=41.3 SD=31.5
CHART_LIKE M=48.6 SD=26.4
CHART_BEAUTY M=49.5 SD=28.9
if(GRAPH_SAVE){
  save_tt(t, output="figs/CHI/fig_5_descriptives.png", overwrite = TRUE)
}

TODO (FIG 6) — Exploratory Factor Analysis

BLOCK 2 DATA

(5.4) — Design Features Index Social Attributions

As Reported in Section 5.4, here we visualize the semantic differential scale survey questions for each stimulus in randomization block #2

#DEFINE BLOCK 2 STIMULI
stimuli <- c("B2-1" ,"B2-2", "B2-3", "B2-4")
graphs <- list()

## LOOP THROUGH EACH STIMULUS IN LIST
i = 0

for (s in stimuli){
  i = i+1
  
  # setup titles 
  title <- ref_stimuli %>% filter(ID == s) %>% select(NAME)  ##TODO IF NOT WORK ref_stim_id
  title <- paste(s,"|",title)

  # setup dataframe
  df <- df_sd_questions_long %>% select(1:8, STIMULUS, QUESTION, STIMULUS_CATEGORY, value) %>% filter(STIMULUS == s)
  d <- left_join( x = df, y = ref_labels, 
                  by = c("QUESTION" = "ref_sd_questions")) %>% 
        mutate(
               category=factor(category, levels=c("COMPETENCY","MAKER","CHART")),
          QUESTION = factor(QUESTION, levels=ref_sd_questions)) %>% 
    group_by(QUESTION) %>% 
    mutate(m=median(value)) ## calc median for printing on graph

  # GGDIST HALFEYE (raincloud doesn't work b/c long tails)
  (g <- d %>%
      ggplot(aes(y = fct_rev(QUESTION), x = value, fill=category)) +
    stat_halfeye(scale=0.8, density="bounded", point_interval = "median_qi", normalize="xy") +
    
    ## MEDIAN
    stat_summary(fun=median, geom="text", fontface = "bold", size= 2.2,
                vjust=+2, hjust = 0.50, aes(label=round(m, digits=0)))+
    stat_summary(fun=median, geom="point", size=2) +
    scale_color_manual(values = my_palettes(name="greys", direction = "1"))+
    scale_fill_manual(values = my_palettes(name="greys", direction = "1"))+
    guides(
      y = guide_axis_manual(labels = rev(ref_labels$left), title = ""),
      y.sec = guide_axis_manual(labels = rev(ref_labels$right))
    ) +
  cowplot::draw_text(text = ref_sd_questions, x = 90, y= ref_sd_questions,size = 8, vjust=-2) +
  labs (title = title, y = "", caption = "(point is median)") +
  theme_minimal() + easy_remove_legend()
)
  
  graphs[[i]] <- g

  if(GRAPH_SAVE == TRUE){ 
  ggsave(plot = g, path="figs/CHI/", filename =paste0("fig_7_",s,"_ggdist.png"), units = c("in"), width = 10, height = 14,bg='#ffffff'   )
  }


} ## END LOOP 

graphs
## [[1]]

## 
## [[2]]

## 
## [[3]]

## 
## [[4]]

PREDICTING TRUST

OTHER BLOCKS

In addition to the descriptive analysis of stimuli in Block 2 that is reported in the manuscript, here we create visualize the semantic differential scale for each stimulus in Study 2.

SD questions for each stimulus

#DEFINE STIMULI
df <- df_graphs
stimuli <- levels(df$STIMULUS)
graphs <- list()


## LOOP THROUGH EACH STIMULUS IN LIST
i = 0

for (s in stimuli){
  i = i+1
  
  # setup titles 
  title <- ref_stimuli %>% filter(ID == s) %>% select(NAME)  ##TODO IF NOT WORK ref_stim_id
  title <- paste(s,"|",title)

  # setup dataframe
  df <- df_sd_questions_long %>% select(1:8, STIMULUS, QUESTION, STIMULUS_CATEGORY, value) %>% filter(STIMULUS == s)
  d <- left_join( x = df, y = ref_labels, 
                  by = c("QUESTION" = "ref_sd_questions")) %>% 
        mutate(
               category=factor(category, levels=c("COMPETENCY","MAKER","CHART")),
          QUESTION = factor(QUESTION, levels=ref_sd_questions)) %>% 
    group_by(QUESTION) %>% 
    mutate(m=median(value)) ## calc median for printing on graph

  # GGDIST HALFEYE (raincloud doesn't work b/c long tails)
  (g <- d %>%
      ggplot(aes(y = fct_rev(QUESTION), x = value, fill=category)) +
    stat_halfeye(scale=0.8, density="bounded", point_interval = "median_qi", normalize="xy") +
    
    ## MEDIAN
    stat_summary(fun=median, geom="text", fontface = "bold", size= 2.2,
                vjust=+2, hjust = 0.50, aes(label=round(m, digits=0)))+
    stat_summary(fun=median, geom="point", size=2) +
    scale_color_manual(values = my_palettes(name="greys", direction = "1"))+
    scale_fill_manual(values = my_palettes(name="greys", direction = "1"))+
    guides(
      y = guide_axis_manual(labels = rev(ref_labels$left), title = ""),
      y.sec = guide_axis_manual(labels = rev(ref_labels$right))
    ) +
  cowplot::draw_text(text = ref_sd_questions, x = 90, y= ref_sd_questions,size = 8, vjust=-2) +
  labs (title = title, y = "", caption = "(point is median)") +
  theme_minimal() + easy_remove_legend()
)

  graphs[[i]] <- g
  
  if(GRAPH_SAVE == TRUE){ 
  ggsave(plot = g, path="figs/CHI/other_blocks/", filename =paste0(s,"_ggdist.png"), units = c("in"), width = 10, height = 14,  bg='#ffffff'  )}
  

  
  
} ## END LOOP 

graphs
## [[1]]

## 
## [[2]]

## 
## [[3]]

## 
## [[4]]

## 
## [[5]]

## 
## [[6]]

## 
## [[7]]

## 
## [[8]]

## 
## [[9]]

## 
## [[10]]

## 
## [[11]]

## 
## [[12]]

## 
## [[13]]

## 
## [[14]]

## 
## [[15]]

## 
## [[16]]

## 
## [[17]]

## 
## [[18]]

## 
## [[19]]

## 
## [[20]]

## 
## [[21]]

## 
## [[22]]

## 
## [[23]]

## 
## [[24]]

## 
## [[25]]

RESOURCES

sessionInfo()
## R version 4.3.2 (2023-10-31)
## Platform: x86_64-apple-darwin20 (64-bit)
## Running under: macOS Sonoma 14.6.1
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.3-x86_64/Resources/lib/libRblas.0.dylib 
## LAPACK: /Library/Frameworks/R.framework/Versions/4.3-x86_64/Resources/lib/libRlapack.dylib;  LAPACK version 3.11.0
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## time zone: America/New_York
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] rstatix_0.7.2      kSamples_1.2-10    SuppDists_1.1-9.7  latex2exp_0.9.6   
##  [5] equatiomatic_0.3.3 lmerTest_3.1-3     lme4_1.1-35.1      Matrix_1.6-5      
##  [9] sjPlot_2.8.15      see_0.8.2          report_0.5.8       parameters_0.21.5 
## [13] performance_0.10.9 modelbased_0.8.7   insight_0.19.9     effectsize_0.8.6  
## [17] datawizard_0.9.1   correlation_0.8.4  bayestestR_0.13.2  easystats_0.7.0   
## [21] jtools_2.2.2       tidygraph_1.3.1    ggraph_2.2.1       interactions_1.2.0
## [25] ggsankey_0.0.99999 lessR_4.3.1        paletteer_1.6.0    plotly_4.10.4     
## [29] RColorBrewer_1.1-3 viridis_0.6.5      viridisLite_0.4.2  ggdist_3.3.2      
## [33] patchwork_1.2.0    ggh4x_0.2.8        ggeasy_0.1.4       corrplot_0.94     
## [37] GGally_2.2.1       gghalves_0.1.4     ggstatsplot_0.12.2 ggformula_0.12.0  
## [41] ggridges_0.5.6     scales_1.3.0       kableExtra_1.4.0   qacBase_1.0.3     
## [45] webshot2_0.1.1     tinytable_0.4.0    summarytools_1.0.1 magrittr_2.0.3    
## [49] lubridate_1.9.3    forcats_1.0.0      stringr_1.5.1      dplyr_1.1.4       
## [53] purrr_1.0.2        readr_2.1.5        tidyr_1.3.1        tibble_3.2.1      
## [57] ggplot2_3.5.0      tidyverse_2.0.0    psych_2.4.1        Hmisc_5.1-2       
## 
## loaded via a namespace (and not attached):
##   [1] matrixStats_1.2.0      httr_1.4.7             numDeriv_2016.8-1.1   
##   [4] tools_4.3.2            backports_1.4.1        sjlabelled_1.2.0      
##   [7] utf8_1.2.4             R6_2.5.1               statsExpressions_1.5.3
##  [10] lazyeval_0.2.2         withr_3.0.0            gridExtra_2.3         
##  [13] textshaping_0.3.7      cli_3.6.2              labeling_0.4.3        
##  [16] sass_0.4.9             BWStest_0.2.3          mvtnorm_1.2-4         
##  [19] robustbase_0.99-2      systemfonts_1.0.6      foreign_0.8-86        
##  [22] svglite_2.1.3          labelled_2.12.0        rstudioapi_0.15.0     
##  [25] generics_0.1.3         car_3.1-2              distributional_0.4.0  
##  [28] zip_2.3.1              leaps_3.1              interp_1.1-6          
##  [31] fansi_1.0.6            abind_1.4-5            lifecycle_1.0.4       
##  [34] yaml_2.3.8             carData_3.0-5          grid_4.3.2            
##  [37] promises_1.2.1         crayon_1.5.2           miniUI_0.1.1.1        
##  [40] lattice_0.22-5         cowplot_1.1.3          haven_2.5.4           
##  [43] chromote_0.3.1         magick_2.8.3           zeallot_0.1.0         
##  [46] pillar_1.9.0           knitr_1.45             tcltk_4.3.2           
##  [49] boot_1.3-30            estimability_1.5       codetools_0.2-19      
##  [52] glue_1.7.0             data.table_1.15.2      vctrs_0.6.5           
##  [55] png_0.1-8              gtable_0.3.4           rematch2_2.1.2        
##  [58] cachem_1.0.8           xfun_0.42              openxlsx_4.2.5.2      
##  [61] mime_0.12              coda_0.19-4.1          gmp_0.7-4             
##  [64] ellipsis_0.3.2         nlme_3.1-164           bslib_0.6.1           
##  [67] rpart_4.1.23           colorspace_2.1-0       nnet_7.3-19           
##  [70] mnormt_2.1.1           tidyselect_1.2.1       processx_3.8.4        
##  [73] emmeans_1.10.0         compiler_4.3.2         htmlTable_2.4.2       
##  [76] xml2_1.3.6             mosaicCore_0.9.4.0     checkmate_2.3.1       
##  [79] DEoptimR_1.1-3         multcompView_0.1-10    digest_0.6.35         
##  [82] minqa_1.2.6            rmarkdown_2.26         htmltools_0.5.7       
##  [85] pkgconfig_2.0.3        jpeg_0.1-10            base64enc_0.1-3       
##  [88] highr_0.10             fastmap_1.1.1          rlang_1.1.3           
##  [91] htmlwidgets_1.6.4      pryr_0.1.6             shiny_1.8.0           
##  [94] farver_2.1.1           jquerylib_0.1.4        jsonlite_1.8.8        
##  [97] rapportools_1.1        Formula_1.2-5          munsell_0.5.0         
## [100] Rcpp_1.0.12            stringi_1.8.3          MASS_7.3-60.0.1       
## [103] plyr_1.8.9             ggstats_0.5.1          parallel_4.3.2        
## [106] ggrepel_0.9.5          sjmisc_2.8.9           deldir_2.0-4          
## [109] graphlayouts_1.1.1     PMCMRplus_1.9.10       ggeffects_1.5.0       
## [112] splines_4.3.2          pander_0.6.5           hms_1.1.3             
## [115] sjstats_0.18.2         ps_1.7.6               igraph_2.0.3          
## [118] reshape2_1.4.4         evaluate_0.23          latticeExtra_0.6-30   
## [121] modelr_0.1.11          nloptr_2.0.3           tzdb_0.4.0            
## [124] tweenr_2.0.3           httpuv_1.6.14          polyclip_1.10-7       
## [127] ggforce_0.4.2          ggExtra_0.10.1         broom_1.0.5           
## [130] xtable_1.8-4           Rmpfr_0.9-5            ggcorrplot_0.1.4.1    
## [133] later_1.3.2            ragg_1.3.0             websocket_1.4.2       
## [136] memoise_2.0.1          ellipse_0.5.0          cluster_2.1.6         
## [139] timechange_0.3.0